//
// Copyright 2022 The Sparrow Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "sparrow/file.h"
#include "src/os/platform.h"

#include <stdio.h>

namespace sparrow {

#ifdef _WIN32
namespace api {
Handle NewFile(const std::string &path, int op, bool share_read) {
    DWORD dwDesiredAccess = 0;
    if (op & option::READ_ONLY) {
        dwDesiredAccess = GENERIC_READ;
    } else if (op & option::WRITE_ONLY) {
        dwDesiredAccess = GENERIC_WRITE;
    } else if (op & option::READ_WRITE) {
        dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
    }

    DWORD dwCreationDisposition = 0;
    if (op & option::CREATE) {
        dwCreationDisposition = OPEN_ALWAYS;
    } else if (op & option::TRUNCATE) {
        dwCreationDisposition = TRUNCATE_EXISTING;
    } else {
        dwCreationDisposition = OPEN_EXISTING;
    }
    return CreateFileA(path.c_str(), dwDesiredAccess, share_read ? FILE_SHARE_READ : 0, NULL,
                       dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
}

int32_t Read(Handle fd, void *buff, int32_t size) {
    DWORD numberOfBytesRead = 0;
    if (!::ReadFile(fd, buff, size, &numberOfBytesRead, NULL) && numberOfBytesRead == 0) {
        return -1;
    }
    return static_cast<int32_t>(numberOfBytesRead);
}

int32_t Read(Handle fd, void *buff, int32_t size, int64_t offset) {
    DWORD numberOfBytesRead = 0;
    OVERLAPPED overlapped   = {0};
    overlapped.OffsetHigh   = static_cast<DWORD>(offset >> 32);
    overlapped.Offset       = static_cast<DWORD>(offset);

    if (!::ReadFile(fd, buff, size, &numberOfBytesRead, &overlapped)) {
        DWORD error = ::GetLastError();
        if (error != ERROR_HANDLE_EOF) {
            return -1;
        }
    }
    return static_cast<int32_t>(numberOfBytesRead);
}

int32_t Write(Handle fd, const void *buff, int32_t size, int64_t offset) {
    DWORD NumberOfBytesWritten = 0;
    OVERLAPPED overlapped      = {0};
    overlapped.OffsetHigh      = static_cast<DWORD>(offset >> 32);
    overlapped.Offset          = static_cast<DWORD>(offset);
    if (!::WriteFile(fd, buff, size, &NumberOfBytesWritten, &overlapped)) {
        return -1;
    }
    return static_cast<int32_t>(NumberOfBytesWritten);
}
int32_t Write(Handle fd, const void *buff, int32_t size) {
    DWORD NumberOfBytesWritten = 0;
    if (!::WriteFile(fd, buff, size, &NumberOfBytesWritten, NULL) && NumberOfBytesWritten == 0) {
        return -1;
    }
    return static_cast<int32_t>(NumberOfBytesWritten);
}
bool Close(Handle fd) { return ::CloseHandle(fd) != FALSE; }

int64_t GetPosition(Handle fd) {
    LARGE_INTEGER distanceToMove = {0};
    LARGE_INTEGER newFilePointer = {0};
    if (::SetFilePointerEx(fd, distanceToMove, &newFilePointer, FILE_END)) {
        return static_cast<int64_t>(newFilePointer.QuadPart);
    }
    return -1;
}

bool SetPosition(Handle fd, int64_t offset, where wh) {
    DWORD dwMoveMethod = 0;
    switch (wh) {
    case where::BEGIN:
        dwMoveMethod = FILE_BEGIN;
        break;
    case where::CURRENT:
        dwMoveMethod = FILE_CURRENT;
        break;
    default:
        dwMoveMethod = FILE_END;
        break;
    }
    LARGE_INTEGER distanceToMove;
    distanceToMove.QuadPart = offset;
    if (::SetFilePointerEx(fd, distanceToMove, NULL, dwMoveMethod)) {
        return true;
    }
    return false;
}

bool ReserveFileSpace(Handle fd, int64_t length) {
    LARGE_INTEGER distance;
    distance.QuadPart = length;
    return (::SetFilePointerEx(fd, distance, NULL, FILE_BEGIN) != FALSE &&
            ::SetEndOfFile(fd) != FALSE);
}

bool ReserveFileSpace(Handle fd, int64_t offset, int64_t length) {
    LARGE_INTEGER distance;
    distance.QuadPart = offset + length;
    return (::SetFilePointerEx(fd, distance, NULL, FILE_BEGIN) != FALSE &&
            ::SetEndOfFile(fd) != FALSE);
}

int64_t GetFileSize(Handle fd) {
    LARGE_INTEGER file_size = {0};
    if (GetFileSizeEx(fd, &file_size)) {
        return static_cast<int64_t>(file_size.QuadPart);
    }
    return -1;
}

bool ResizeFile(Handle fd, int64_t size) { return ReserveFileSpace(fd, size); }
} // namespace api

int64_t GetFileSize(const std::string &p) {
    WIN32_FILE_ATTRIBUTE_DATA fad;
    if (!GetFileAttributesExA(p.c_str(), GetFileExInfoStandard, &fad)) {
        return static_cast<int64_t>(-1);
    }
    if ((fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
        return static_cast<int64_t>(-1);
    }
    return (static_cast<int64_t>(fad.nFileSizeHigh) << (sizeof(fad.nFileSizeLow) * 8)) +
           fad.nFileSizeLow;
}

bool DeleteFile(const std::string &p) {
    if (!::DeleteFileA(p.c_str())) {
        return false;
    }
    return true;
}

bool FileExists(const std::string &p) {
    return GetFileAttributesA(p.c_str()) != INVALID_FILE_ATTRIBUTES;
}
bool RenameFile(const std::string &src, const std::string &target) {
    if (::MoveFileA(src.c_str(), target.c_str())) {
        return true;
    }

    if (::ReplaceFileA(target.c_str(), src.c_str(), NULL, REPLACEFILE_IGNORE_MERGE_ERRORS, NULL,
                       NULL)) {
        return true;
    }
    return false;
}

bool CreateDirectory(const std::string &dir) {
    if (!::CreateDirectoryA(dir.c_str(), NULL)) {
        return false;
    }
    return true;
}
bool DeleteDirectory(const std::string &dir) {
    if (!::RemoveDirectoryA(dir.c_str())) {
        return false;
    }
    return true;
}
#else
namespace api {
Handle NewFile(const std::string &path, int op, bool share_read) {
    int flags = 0;

    if (op & option::READ_ONLY) {
        flags |= O_RDONLY;
    } else if (op & option::WRITE_ONLY) {
        flags |= O_WRONLY;
    } else if (op & option::READ_WRITE) {
        flags |= O_RDWR;
    }

    if (op & option::CREATE) {
        flags |= O_CREAT;
    } else if (op & option::TRUNCATE) {
        flags |= O_TRUNC;
    }

    int prms = 0;
    if (share_read) {
        prms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    } else {
        prms = S_IRUSR | S_IWUSR;
    }
    return open(path.c_str(), flags, prms);
}

int32_t Read(Handle fd, void *buff, int32_t size, int64_t offset) {
    return static_cast<int32_t>(::pread(fd, buff, size, offset));
}
int32_t Read(Handle fd, void *buff, int32_t size) {
    return static_cast<int32_t>(::read(fd, buff, size));
}
int32_t Write(Handle fd, const void *buff, int32_t size, int64_t offset) {
    return static_cast<int32_t>(::pwrite(fd, buff, size, offset));
}
int32_t Write(Handle fd, const void *buff, int32_t size) {
    return static_cast<int32_t>(::write(fd, buff, size));
}
bool Close(Handle fd) { return ::close(fd) == 0; }
int64_t GetPosition(Handle fd) { return static_cast<int64_t>(::lseek(fd, 0, SEEK_CUR)); }

bool SetPosition(Handle fd, int64_t offset, where wh) {
    int move_method = 0;
    switch (wh) {
    case where::BEGIN:
        move_method = SEEK_SET;
        break;
    case where::CURRENT:
        move_method = SEEK_CUR;
        break;
    default:
        move_method = SEEK_END;
        break;
    }
    return ::lseek(fd, offset, move_method) != -1;
}

bool ReserveFileSpace(Handle fd, int64_t length) { return ::posix_fallocate(fd, 0, length) == 0; }

bool ReserveFileSpace(Handle fd, int64_t offset, int64_t length) {
    return ::posix_fallocate(fd, offset, length) == 0;
}

int64_t GetFileSize(Handle fd) {
    int64_t backup = GetPosition(fd);
    if (backup == -1 || !SetPosition(fd, 0, api::where::END)) {
        return -1;
    }
    int64_t size = GetPosition(fd);
    SetPosition(fd, backup, api::where::BEGIN);
    return size;
}

bool ResizeFile(Handle fd, int64_t size) { return ::ftruncate(fd, size) == 0; }
} // namespace api

int64_t GetFileSize(const std::string &p) {
    struct stat path_stat;
    if (::stat(p.c_str(), &path_stat) != 0) {
        return static_cast<int64_t>(-1);
    }
    if (!S_ISREG(path_stat.st_mode)) {
        return static_cast<int64_t>(-1);
    }
    return static_cast<int64_t>(path_stat.st_size);
}
bool DeleteFile(const std::string &p) { return ::unlink(p.c_str()) == 0; }

bool FileExists(const std::string &p) { return ::access(p.c_str(), F_OK) == 0; }
bool RenameFile(const std::string &src, const std::string &target) {
    return std::rename(src.c_str(), target.c_str()) == 0;
}
bool CreateDirectory(const std::string &dir) { return ::mkdir(dir.c_str(), 0755) == 0; }
bool DeleteDirectory(const std::string &dir) { return ::rmdir(dir.c_str()) == 0; }
#endif

// ------------------------
BaseFile::BaseFile()
    : fd_(api::INVALID_FILE_DESCRIPTOR) {}

BaseFile::~BaseFile() {
    if (IsValidFileHandle(fd_)) {
        api::Close(fd_);
    }
}

bool BaseFile::Init(const std::string &name) {
    return Init(name, api::option::READ_WRITE | api::option::CREATE | api::option::TRUNCATE, true);
}

bool BaseFile::Init(const std::string &name, int option, bool share_read) {
    this->Reset();
    fd_ = api::NewFile(name, option, share_read);
    return IsValidFileHandle(fd_);
}

int32_t BaseFile::Read(void *buff, int32_t len) { return api::Read(fd_, buff, len); }
int32_t BaseFile::Read(void *buff, int32_t size, int64_t offset) {
    return api::Read(fd_, buff, size, offset);
}
int32_t BaseFile::Write(const void *buff, int32_t len) { return api::Write(fd_, buff, len); }
int32_t BaseFile::Write(const void *buff, int32_t len, int64_t offset) {
    return api::Write(fd_, buff, len, offset);
}
int64_t BaseFile::GetPosition() { return api::GetPosition(fd_); }

bool BaseFile::SetPosition(int64_t offset, api::where where) {
    return api::SetPosition(fd_, offset, where);
}

bool BaseFile::Reserve(int64_t length) { return api::ReserveFileSpace(fd_, length); }

void BaseFile::Reset() {
    if (IsValidFileHandle(fd_)) {
        api::Close(fd_);
        fd_ = api::INVALID_FILE_DESCRIPTOR;
    }
}

} // namespace sparrow
